package com.suyl.designpattern.aa_singletonpattern;

/**
 * 双重校验锁
 * <p>
 * <p>
 * 问题分析
 *   那么，我们可以看出和之前的2中实现，有几点不同，解释如下。
 * 第一次判断singleton是否为null
 *   第一次判断是在Synchronized同步代码块外进行判断，由于单例模式只会创建一个实例，并通过getInstance方法返回
 * singleton对象，所以，第一次判断，是为了在singleton对象已经创建的情况下，避免进入同步代码块，提升效率。
 * 第二次判断singleton是否为null
 *   第二次判断是为了避免以下情况的发生。
 *   (1)假设：线程A已经经过第一次判断，判断singleton=null，准备进入同步代码块.
 *   (2)此时线程B获得时间片，犹豫线程A并没有创建实例，所以，判断singleton仍然=null，所以线程B创建了实例singleton。
 *   (3)此时，线程A再次获得时间片，犹豫刚刚经过第一次判断singleton=null(不会重复判断)，进入同步代码块，这个时候，
 * 我们如果不加入第二次判断的话，那么线程A又会创造一个实例singleton，就不满足我们的单例模式的要求，所以第二次判断是很有必要的。
 * 为什么要加Volatile关键字
 *   其实，上面两点比较好理解，第三点，既然有了Synchronized作为限制，为什么还要加入Volatile呢？
 *   首先，我们需要知道Volatile可以保证可见性和原子性，同时保证JVM对指令不会进行重排序。
 *   其次，这点也很关键，对象的创建不是一步完成的，是一个符合操作，需要3个指令。
 *   我们结合这一句代码来解释：
 * singleton = new Singleton();
 *   指令1：获取singleton对象的内存地址
 *   指令2：初始化singleton对象
 *   指令3：将这块内存地址，指向引用变量singleton。
 *   那么，这样我们就比较好理解，为什么要加入Volatile变量了。由于Volatile禁止JVM对指令进行重排序。所以创建对象的过程仍然会按照指令1-2-3的有序执行。
 *   反之，如果没有Volatile关键字，假设线程A正常创建一个实例，那么指定执行的顺序可能2-1-3，当执行到指令1的时候，
 * 线程B执行getInstance方法，获取到的，可能是对象的一部分，或者是不正确的对象，程序可能就会报异常信息。
 */
public class ag_Singleton {

    private volatile static ag_Singleton singleton;

    private ag_Singleton() {
    }

    public static ag_Singleton getSingleton() {
        if (singleton == null) {
            synchronized (ag_Singleton.class) {
                if (singleton == null) {
                    singleton = new ag_Singleton();
                }
            }
        }
        return singleton;
    }
}
